﻿using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using NewRetail.Application.Components.Goods.Entity;
using NewRetail.Application.Components.Platform.Entity;
using NewRetail.Application.Core;
using Polly;
using System;
using System.Data;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Services;
using Volo.Abp.Data;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Uow;

namespace NewRetail.Application.Components.Platform
{
    public interface INoGeneratorService : IApplicationService
    {
        Task<string> GenerateGeneralNoAsync(string prefix, int? len = null);
        Task<string> GenerateReceiptNoAsync(string prefix, int? payloadLen = null);
        Task<string> GenerateGoodsCategoryNoAsync(string parentId);
        Task<string> GenerateGoodsNoAsync(string categoryId, int? len = null);

        Task<int> GenerateNoWithOptimisticLockAndRetryAsync(string category);
        Task<int> GenerateNoWithDistributedLockAsync(string category);
    }

    public class NoGeneratorService : MyApplicationService, INoGeneratorService
    {
        private readonly RedLockFactoryWrapper redLockFactoryWrapper;

        private readonly IRepository<SequenceEntity> sequenceEntities;
        private readonly IRepository<GoodsCategory> goodsCategories;
        private readonly IRepository<Warehouse.Entity.Warehouse> warehouses;

        public NoGeneratorService(
            RedLockFactoryWrapper redLockFactoryWrapper,
            IRepository<SequenceEntity> sequenceEntities,
            IRepository<GoodsCategory> goodsCategories,
            IRepository<Warehouse.Entity.Warehouse> warehouses)
        {
            this.goodsCategories = goodsCategories;
            this.redLockFactoryWrapper = redLockFactoryWrapper;
            this.sequenceEntities = sequenceEntities;
            this.warehouses = warehouses;
        }

        /// <summary>
        /// yyyy-MM-dd HH:mm:ss
        /// </summary>
        /// <returns></returns>
        string ChineseDateString() => this.Clock.Now.ToString("yyyyMMdd");
        string ChineseMonthString() => this.Clock.Now.ToString("yyyyMM");

        public async Task<string> GenerateReceiptNoAsync(string prefix, int? len)
        {
            prefix ??= string.Empty;
            prefix = prefix.Trim().ToUpper();

            len ??= 5;

            var month = this.ChineseMonthString();
            var no = await this.GenerateNoWithDistributedLockAsync(category: $"{prefix}-{month}");
            var addon = no.ToString().PadLeft(len.Value, '0');

            return $"{prefix}{month}{addon}";
        }

        public async Task<string> GenerateGeneralNoAsync(string prefix, int? len)
        {
            prefix ??= string.Empty;
            prefix = prefix.Trim().ToUpper();

            len ??= 5;

            var no = await this.GenerateNoWithDistributedLockAsync(category: prefix);
            var addon = no.ToString().PadLeft(len.Value, '0');

            return $"{prefix}{addon}";
        }

        public async Task<string> GenerateGoodsCategoryNoAsync(string parentId)
        {
            var parent = await this.goodsCategories.FirstOrDefaultAsync(x => x.Id == parentId);

            var parentNo = parent?.No;

            var prefix = parentNo ?? "first-level";

            var no = await this.GenerateNoWithDistributedLockAsync($"goods-category-{prefix}");

            return $"{parentNo}{no.ToString().PadLeft(2, '0')}";
        }

        public async Task<string> GenerateGoodsNoAsync(string categoryId, int? len)
        {
            categoryId.Should().NotBeNullOrEmpty();

            var categoryEntity = await this.goodsCategories.FirstOrDefaultAsync(x => x.Id == categoryId);
            if (categoryEntity == null)
            {
                throw new UserFriendlyException("商品分类不存在");
            }
            //update by cl 20221225
            var no = await this.GenerateNoWithDistributedLockAsync($"goods-no-{categoryId}");
            //var no = await this.GenerateNoWithDistributedLockAsync("goods-no");

            len ??= 6;

            var noStr = $"{no}".PadLeft(len.Value, '0');

            //add by cl 20230130
            var warehouse = await this.warehouses.FirstOrDefaultAsync(x => x.Id == categoryEntity.warehouseID);
            if (warehouse == null)
            {
                throw new UserFriendlyException("仓库不存在");
            }
            var wNo = warehouse.No.Substring(warehouse.No.Length - 2, 2);
            //判断6位流水的第一位是否大于0，如果大于0则需要返回值多出一位
            if (Convert.ToInt32(noStr.Substring(0, 1)) > 0)
              return $"{categoryEntity.No}{wNo}{noStr}";

            return $"{categoryEntity.No}{wNo}{noStr.Substring(1)}";
            
            //return $"{categoryEntity.No}-{noStr}";
        }

        async Task<int> GenerateNoWithOptimisticLockAsync(string category)
        {
            using var uow = this.UnitOfWorkManager.Begin(requiresNew: true);
            try
            {
                var db = await this.sequenceEntities.GetDbContextAsync();
                var set = db.Set<SequenceEntity>();

                var entity = await set.AsTracking().FirstOrDefaultAsync(x => x.Category == category);
                if (entity == null)
                {
                    entity = new SequenceEntity();
                    this.EntityHelper.InitEntity(entity);
                    entity.Category = category;
                    entity.NextId = 1;

                    set.Add(entity);

                    await db.SaveChangesAsync();
                }
                else
                {
                    entity.NextId += 1;
                    this.EntityHelper.SetUpdationInfo(entity);

                    await db.SaveChangesAsync();
                }

                await uow.CompleteAsync();

                return entity.NextId;
            }
            catch (Exception e) when (this.IsDbConcurrencyException(e))
            {
                await uow.RollbackAsync();
                throw;
            }
            catch
            {
                await uow.RollbackAsync();
                throw;
            }
        }

        bool IsDbConcurrencyException(Exception e) =>
           e is DBConcurrencyException ||
           e is DbUpdateConcurrencyException ||
           e is AbpDbConcurrencyException;

        public async Task<int> GenerateNoWithOptimisticLockAndRetryAsync(string category)
        {
            var retryPolicy = Policy.Handle<Exception>(exceptionPredicate: this.IsDbConcurrencyException)
                .WaitAndRetryAsync(retryCount: 3, i => TimeSpan.FromMilliseconds(100 * i));

            var nextId = await retryPolicy.ExecuteAsync(() => this.GenerateNoWithOptimisticLockAsync(category));

            return nextId;
        }

        public async Task<int> GenerateNoWithDistributedLockAsync(string category)
        {
            category.Should().NotBeNullOrEmpty();
            using var distributedLock = await this.redLockFactoryWrapper.RedLockFactory.CreateLockAsync(
                resource: $"{nameof(NoGeneratorService)}.{nameof(GenerateNoWithDistributedLockAsync)}.category={category}",
                expiryTime: TimeSpan.FromSeconds(30),
                waitTime: TimeSpan.FromSeconds(5),
                retryTime: TimeSpan.FromSeconds(1));

            if (distributedLock.IsAcquired)
            {
                return await this.GenerateNoWithOptimisticLockAndRetryAsync(category);
            }
            else
            {
                throw new UserFriendlyException("无法生成id，未能获取分布式锁");
            }
        }
    }
}
